<?php
abstract class system_db_table_row_abstract implements ArrayAccess, IteratorAggregate {
	protected $_data = array();
	protected $_cleanData = array();
	protected $_modifiedFields = array();
	protected $_table = null;
	protected $_connected = true;
	protected $_readOnly = false;
	protected $_tableClass = null;
	protected $_primary;
	
	public function __construct(array $config = array()) {
		if(isset($config['table'])&&$config['table'] instanceof system_db_table_abstract) {
			$this->_table = $config['table'];
			$this->_tableClass = get_class($this->_table);
		} elseif($this->_tableClass!==null) {
			$this->_table = $this->_getTableFromString($this->_tableClass);
		}
		
		if(isset($config['data'])) {
			if(!is_array($config['data'])) {
				throw new system_db_table_row_exception('Data must be an array');
			}
			$this->_data = $config['data'];
		}
		if(isset($config['stored'])&&$config['stored']===true) {
			$this->_cleanData = $this->_data;
		}
		
		if(isset($config['readOnly'])&&$config['readOnly']===true) {
			$this->setReadOnly(true);
		}
		
		// Retrieve primary keys from table schema
		if(!!($table = $this->_getTable())) {
			$info = $table->info();
			$this->_primary = (array) $info['primary'];
		}
		
		$this->init();
	}
	
	protected function _transformColumn($columnName) {
		if(!is_string($columnName)) {
			throw new system_db_table_row_exception('Specified column is not a string');
		}
		// Perform no transformation by default
		return $columnName;
	}
	
	public function __get($columnName) {
		$columnName = $this->_transformColumn($columnName);
		if(!array_key_exists($columnName, $this->_data)) {
			throw new system_db_table_row_exception("Specified column \"$columnName\" is not in the row");
		}
		return $this->_data[$columnName];
	}
	//修改数值
	public function __set($columnName, $value) {
		$columnName = $this->_transformColumn($columnName);
		if(!array_key_exists($columnName, $this->_data)) {
			throw new system_db_table_row_exception("Specified column \"$columnName\" is not in the row");
		}
		$this->_data[$columnName] = $value;
		$this->_modifiedFields[$columnName] = true;
	}
	
	public function __unset($columnName) {
		$columnName = $this->_transformColumn($columnName);
		if(!array_key_exists($columnName, $this->_data)) {
			throw new system_db_table_row_exception("Specified column \"$columnName\" is not in the row");
		}
		if($this->isConnected()&&in_array($columnName, $this->_table->info('primary'))) {
			throw new system_db_table_row_exception("Specified column \"$columnName\" is a primary key and should not be unset");
		}
		unset($this->_data[$columnName]);
		return $this;
	}
	
	public function __isset($columnName) {
		$columnName = $this->_transformColumn($columnName);
		return array_key_exists($columnName, $this->_data);
	}
	
	//serialized 化对象时的操作
	public function __sleep() {
		return array('_tableClass', '_primary', '_data', '_cleanData', '_readOnly', '_modifiedFields');
	}
	public function __wakeup() {
		$this->_connected = false;
	}
	
	/**
	 * Proxy to __isset
	 * Required by the ArrayAccess implementation
	 *
	 * @param string $offset
	 * @return boolean
	 */
	public function offsetExists($offset) {
		return $this->__isset($offset);
	}
	/**
	 * Proxy to __get
	 * Required by the ArrayAccess implementation
	 *
	 * @param string $offset
	 * @return string
	 */
	public function offsetGet($offset) {
		return $this->__get($offset);
	}
	/**
	 * Proxy to __set
	 * Required by the ArrayAccess implementation
	 *
	 * @param string $offset
	 * @param mixed $value
	 */
	public function offsetSet($offset, $value) {
		$this->__set($offset, $value);
	}
	
	/**
	 * Proxy to __unset
	 * Required by the ArrayAccess implementation
	 *
	 * @param string $offset
	 */
	public function offsetUnset($offset) {
		return $this->__unset($offset);
	}
	
	/**
	 * Initialize object
	 *
	 * Called from {@link __construct()} as final step of object instantiation.
	 *
	 * @return void
	 */
	public function init() {
	}
	
	public function getTable() {
		return $this->_table;
	}
	
	/**
	 * Set the table object, to re-establish a live connection
	 * to the database for a Row that has been de-serialized.
	 *
	 * @param Zend_Db_Table_Abstract $table
	 * @return boolean
	 * @throws Zend_Db_Table_Row_Exception
	 */
	public function setTable(system_db_table_abstract $table = null) {
		if($table==null) {
			$this->_table = null;
			$this->_connected = false;
			return false;
		}
		
		$tableClass = get_class($table);
		if(!$table instanceof $this->_tableClass) {
			throw new system_db_table_row_exception("The specified Table is of class $tableClass, expecting class to be instance of $this->_tableClass");
		}
		
		$this->_table = $table;
		$this->_tableClass = $tableClass;
		
		$info = $this->_table->info();
		
		if($info['cols']!=array_keys($this->_data)) {
			throw new system_db_table_row_exception('The specified Table does not have the same columns as the Row');
		}
		
		if(!array_intersect((array) $this->_primary, $info['primary'])==(array) $this->_primary) {
			throw new system_db_table_row_exception("The specified Table '$tableClass' does not have the same primary key as the Row");
		}
		
		$this->_connected = true;
		return true;
	}
	
	public function getTableClass() {
		return $this->_tableClass;
	}
	
	/**
	 * Test the connected status of the row.
	 *
	 * @return boolean
	 */
	public function isConnected() {
		return $this->_connected;
	}
	
	public function isReadOnly() {
		return $this->_readOnly;
	}
	
	public function setReadOnly($flag) {
		$this->_readOnly = (bool) $flag;
	}
	public function select() {
		return $this->getTable()->select();
	}
	
	public function save() {
		/**
		 * If the _cleanData array is empty,
		 * this is an INSERT of a new row.
		 * Otherwise it is an UPDATE.
		 */
		if(empty($this->_cleanData)) {
			return $this->_doInsert();
		} else {
			return $this->_doUpdate();
		}
	}
	
	protected function _doInsert() {
		/**
		 * A read-only row cannot be saved.
		 */
		if($this->_readOnly===true) {
			throw new system_db_table_row_exception('This row has been marked read-only');
		}
		
		/**
		 * Run pre-INSERT logic
		 */
		$this->_insert();
		
		/**
		 * Execute the INSERT (this may throw an exception)
		 */
		$data = array_intersect_key($this->_data, $this->_modifiedFields);
		$primaryKey = $this->_getTable()->insert($data);
		
		/**
		 * Normalize the result to an array indexed by primary key column(s).
		 * The table insert() method may return a scalar.
		 */
		if(is_array($primaryKey)) {
			$newPrimaryKey = $primaryKey;
		} else {
			//ZF-6167 Use tempPrimaryKey temporary to avoid that zend encoding fails.
			$tempPrimaryKey = (array) $this->_primary;
			$newPrimaryKey = array(current($tempPrimaryKey) => $primaryKey);
		}
		
		/**
		 * Save the new primary key value in _data.  The primary key may have
		 * been generated by a sequence or auto-increment mechanism, and this
		 * merge should be done before the _postInsert() method is run, so the
		 * new values are available for logging, etc.
		 */
		$this->_data = array_merge($this->_data, $newPrimaryKey);
		
		/**
		 * Run post-INSERT logic
		 */
		$this->_postInsert();
		
		/**
		 * Update the _cleanData to reflect that the data has been inserted.
		 */
		$this->_refresh();
		
		return $primaryKey;
	}
	
	protected function _doUpdate() {
		/**
		 * A read-only row cannot be saved.
		 */
		if($this->_readOnly===true) {
			throw new system_db_table_row_exception('This row has been marked read-only');
		}
		
		/**
		 * Get expressions for a WHERE clause
		 * based on the primary key value(s).
		 */
		$where = $this->_getWhereQuery(false);
		
		/**
		 * Run pre-UPDATE logic
		 */
		$this->_update();
		
		/**
		 * Compare the data to the modified fields array to discover
		 * which columns have been changed.
		 */
		$diffData = array_intersect_key($this->_data, $this->_modifiedFields);
		
		/**
		 * Were any of the changed columns part of the primary key?
		 */
		$pkDiffData = array_intersect_key($diffData, array_flip((array) $this->_primary));
		
		/**
		 * Execute cascading updates against dependent tables.
		 * Do this only if primary key value(s) were changed.
		 */
		if(count($pkDiffData)>0) {
			$depTables = $this->_getTable()->getDependentTables();
			if(!empty($depTables)) {
				$pkNew = $this->_getPrimaryKey(true);
				$pkOld = $this->_getPrimaryKey(false);
				foreach($depTables as $tableClass) {
					$t = $this->_getTableFromString($tableClass);
					$t->_cascadeUpdate($this->getTableClass(), $pkOld, $pkNew);
				}
			}
		}
		
		/**
		 * Execute the UPDATE (this may throw an exception)
		 * Do this only if data values were changed.
		 * Use the $diffData variable, so the UPDATE statement
		 * includes SET terms only for data values that changed.
		 */
		if(count($diffData)>0) {
			$this->_getTable()->update($diffData, $where);
		}
		
		/**
		 * Run post-UPDATE logic.  Do this before the _refresh()
		 * so the _postUpdate() function can tell the difference
		 * between changed data and clean (pre-changed) data.
		 */
		$this->_postUpdate();
		
		/**
		 * Refresh the data just in case triggers in the RDBMS changed
		 * any columns.  Also this resets the _cleanData.
		 */
		$this->_refresh();
		
		/**
		 * Return the primary key value(s) as an array
		 * if the key is compound or a scalar if the key
		 * is a scalar.
		 */
		$primaryKey = $this->_getPrimaryKey(true);
		if(count($primaryKey)==1) {
			return current($primaryKey);
		}
		
		return $primaryKey;
	}
	
	public function delete() {
		/**
		 * A read-only row cannot be deleted.
		 */
		if($this->_readOnly===true) {
			throw new system_db_table_row_exception('This row has been marked read-only');
		}
		
		$where = $this->_getWhereQuery();
		
		/**
		 * Execute pre-DELETE logic
		 */
		$this->_delete();
		
		/**
		 * Execute cascading deletes against dependent tables
		 */
		//外键关联操作
		$depTables = $this->_getTable()->getDependentTables();
		if(!empty($depTables)) {
			$pk = $this->_getPrimaryKey();
			foreach($depTables as $tableClass) {
				$t = $this->_getTableFromString($tableClass);
				$t->_cascadeDelete($this->getTableClass(), $pk);
			}
		}
		
		/**
		 * Execute the DELETE (this may throw an exception)
		 */
		$result = $this->_getTable()->delete($where);
		
		/**
		 * Execute post-DELETE logic
		 */
		$this->_postDelete();
		
		/**
		 * Reset all fields to null to indicate that the row is not there
		 */
		$this->_data = array_combine(array_keys($this->_data), array_fill(0, count($this->_data), null));
		
		return $result;
	}
	
	public function getIterator() {
		return new ArrayIterator((array) $this->_data);
	}
	
	/**
	 * Returns the column/value data as an array.
	 *
	 * @return array
	 */
	public function toArray() {
		return (array) $this->_data;
	}
	
	public function setFromArray(array $data) {
		$data = array_intersect_key($data, $this->_data);
		
		foreach($data as $columnName => $value) {
			$this->__set($columnName, $value);
		}
		
		return $this;
	}
	
	public function refresh() {
		return $this->_refresh();
	}
	
	protected function _getTable() {
		if(!$this->_connected) {
			throw new system_db_table_row_exception('Cannot save a Row unless it is connected');
		}
		return $this->_table;
	}
	
	protected function _getPrimaryKey($useDirty = true) {
		if(!is_array($this->_primary)) {
			throw new system_db_table_row_exception("The primary key must be set as an array");
		}
		
		$primary = array_flip($this->_primary);
		if($useDirty) {
			$array = array_intersect_key($this->_data, $primary);
		} else {
			$array = array_intersect_key($this->_cleanData, $primary);
		}
		if(count($primary)!=count($array)) {
			throw new system_db_table_row_exception("The specified Table '$this->_tableClass' does not have the same primary key as the Row");
		}
		return $array;
	}
	
	protected function _getWhereQuery($useDirty = true) {
		$where = array();
		$db = $this->_getTable()->getAdapter();
		$primaryKey = $this->_getPrimaryKey($useDirty);
		$info = $this->_getTable()->info();
		$metadata = $info[system_db_table_abstract::METADATA];
		
		// retrieve recently updated row using primary keys
		$where = array();
		foreach($primaryKey as $column => $value) {
			$tableName = $db->quoteIdentifier($info[system_db_table_abstract::NAME], true);
			$type = $metadata[$column]['DATA_TYPE'];
			$columnName = $db->quoteIdentifier($column, true);
			$where[] = $db->quoteInto("{$tableName}.{$columnName} = ?", $value, $type);
		}
		return $where;
	}
	//刷新
	protected function _refresh() {
		$where = $this->_getWhereQuery();
		$row = $this->_getTable()->fetchRow($where);
		
		if(null===$row) {
			throw new system_db_table_row_exception('Cannot refresh row as parent is missing');
		}
		
		$this->_data = $row->toArray();
		$this->_cleanData = $this->_data;
		$this->_modifiedFields = array();
	}
	protected function _insert() {
	}
	
	/**
	 * Allows post-insert logic to be applied to row.
	 * Subclasses may override this method.
	 *
	 * @return void
	 */
	protected function _postInsert() {
	}
	
	/**
	 * Allows pre-update logic to be applied to row.
	 * Subclasses may override this method.
	 *
	 * @return void
	 */
	protected function _update() {
	}
	
	/**
	 * Allows post-update logic to be applied to row.
	 * Subclasses may override this method.
	 *
	 * @return void
	 */
	protected function _postUpdate() {
	}
	
	/**
	 * Allows pre-delete logic to be applied to row.
	 * Subclasses may override this method.
	 *
	 * @return void
	 */
	protected function _delete() {
	}
	
	/**
	 * Allows post-delete logic to be applied to row.
	 * Subclasses may override this method.
	 *
	 * @return void
	 */
	protected function _postDelete() {
	}
	
	/**
	 * Prepares a table reference for lookup.
	 *
	 * Ensures all reference keys are set and properly formatted.
	 *
	 * @param Zend_Db_Table_Abstract $dependentTable
	 * @param Zend_Db_Table_Abstract $parentTable
	 * @param string                 $ruleKey
	 * @return array
	 */
	protected function _prepareReference(system_db_table_abstract $dependentTable, system_db_table_abstract $parentTable, $ruleKey) {
		$parentTableName = (get_class($parentTable)==='system_db_table') ? $parentTable->getDefinitionConfigName() : get_class($parentTable);
		$map = $dependentTable->getReference($parentTableName, $ruleKey);
		
		if(!isset($map[system_db_table_abstract::REF_COLUMNS])) {
			$parentInfo = $parentTable->info();
			$map[system_db_table_abstract::REF_COLUMNS] = array_values((array) $parentInfo['primary']);
		}
		
		$map[system_db_table_abstract::COLUMNS] = (array) $map[system_db_table_abstract::COLUMNS];
		$map[system_db_table_abstract::REF_COLUMNS] = (array) $map[system_db_table_abstract::REF_COLUMNS];
		
		return $map;
	}
	
	/**
	 * Query a dependent table to retrieve rows matching the current row.
	 *
	 * @param string|Zend_Db_Table_Abstract  $dependentTable
	 * @param string                         OPTIONAL $ruleKey
	 * @param Zend_Db_Table_Select           OPTIONAL $select
	 * @return Zend_Db_Table_Rowset_Abstract Query result from $dependentTable
	 * @throws Zend_Db_Table_Row_Exception If $dependentTable is not a table or is not loadable.
	 */
	public function findDependentRowset($dependentTable, $ruleKey = null, system_db_table_select $select = null) {
		$db = $this->_getTable()->getAdapter();
		
		if(is_string($dependentTable)) {
			$dependentTable = $this->_getTableFromString($dependentTable);
		}
		
		if(!$dependentTable instanceof system_db_table_abstract) {
			$type = gettype($dependentTable);
			if(is_object($type)) {
				$type = get_class($dependentTable);
			}
			throw new system_db_table_exception("Dependent table must be a Zend_Db_Table_Abstract, but it is $type");
		}
		
		// even if we are interacting between a table defined in a class and a
		// table via extension, ensure to persist the definition
		if(($tableDefinition = $this->_table->getDefinition())!==null&&($dependentTable->getDefinition()==null)) {
			$dependentTable->setOptions(array(system_db_table_abstract::DEFINITION => $tableDefinition));
		}
		
		if($select===null) {
			$select = $dependentTable->select();
		} else {
			$select->setTable($dependentTable);
		}
		
		$map = $this->_prepareReference($dependentTable, $this->_getTable(), $ruleKey);
		
		for($i = 0; $i<count($map[system_db_table_abstract::COLUMNS]); ++$i) {
			$parentColumnName = $db->foldCase($map[system_db_table_abstract::REF_COLUMNS][$i]);
			$value = $this->_data[$parentColumnName];
			// Use adapter from dependent table to ensure correct query construction
			$dependentDb = $dependentTable->getAdapter();
			$dependentColumnName = $dependentDb->foldCase($map[system_db_table_abstract::COLUMNS][$i]);
			$dependentColumn = $dependentDb->quoteIdentifier($dependentColumnName, true);
			$dependentInfo = $dependentTable->info();
			$type = $dependentInfo[system_db_table_abstract::METADATA][$dependentColumnName]['DATA_TYPE'];
			$select->where("$dependentColumn = ?", $value, $type);
		}
		
		return $dependentTable->fetchAll($select);
	}
	
	/**
	 * Query a parent table to retrieve the single row matching the current row.
	 *
	 * @param string|Zend_Db_Table_Abstract $parentTable
	 * @param string                        OPTIONAL $ruleKey
	 * @param Zend_Db_Table_Select          OPTIONAL $select
	 * @return Zend_Db_Table_Row_Abstract   Query result from $parentTable
	 * @throws Zend_Db_Table_Row_Exception If $parentTable is not a table or is not loadable.
	 */
	public function findParentRow($parentTable, $ruleKey = null, system_db_table_select $select = null) {
		$db = $this->_getTable()->getAdapter();
		
		if(is_string($parentTable)) {
			$parentTable = $this->_getTableFromString($parentTable);
		}
		
		if(!$parentTable instanceof system_db_table_abstract) {
			$type = gettype($parentTable);
			if(is_object($parentTable)) {
				$type = get_class($parentTable);
			}
			throw new system_db_table_row_exception("Parent table must be a system_db_table_bstract, but it is $type");
		}
		
		// even if we are interacting between a table defined in a class and a
		// table via extension, ensure to persist the definition
		if(($tableDefinition = $this->_table->getDefinition())!==null&&($parentTable->getDefinition()==null)) {
			$parentTable->setOptions(array(system_db_table_abstract::DEFINITION => $tableDefinition));
		}
		
		if($select===null) {
			$select = $parentTable->select();
		} else {
			$select->setTable($parentTable);
		}
		
		$map = $this->_prepareReference($this->_getTable(), $parentTable, $ruleKey);
		
		// iterate the map, creating the proper wheres
		for($i = 0; $i<count($map[system_db_table_abstract::COLUMNS]); ++$i) {
			$dependentColumnName = $db->foldCase($map[system_db_table_abstract::COLUMNS][$i]);
			$value = $this->_data[$dependentColumnName];
			// Use adapter from parent table to ensure correct query construction
			$parentDb = $parentTable->getAdapter();
			$parentColumnName = $parentDb->foldCase($map[system_db_table_abstract::REF_COLUMNS][$i]);
			$parentColumn = $parentDb->quoteIdentifier($parentColumnName, true);
			$parentInfo = $parentTable->info();
			
			// determine where part
			$type = $parentInfo[system_db_table_abstract::METADATA][$parentColumnName]['DATA_TYPE'];
			$nullable = $parentInfo[system_db_table_abstract::METADATA][$parentColumnName]['NULLABLE'];
			if($value===null&&$nullable==true) {
				$select->where("$parentColumn IS NULL");
			} elseif($value===null&&$nullable==false) {
				return null;
			} else {
				$select->where("$parentColumn = ?", $value, $type);
			}
		
		}
		
		return $parentTable->fetchRow($select);
	}
	
	/**
	 * @param  string|Zend_Db_Table_Abstract  $matchTable
	 * @param  string|Zend_Db_Table_Abstract  $intersectionTable
	 * @param  string                         OPTIONAL $callerRefRule
	 * @param  string                         OPTIONAL $matchRefRule
	 * @param  Zend_Db_Table_Select           OPTIONAL $select
	 * @return Zend_Db_Table_Rowset_Abstract Query result from $matchTable
	 * @throws Zend_Db_Table_Row_Exception If $matchTable or $intersectionTable is not a table class or is not loadable.
	 */
	public function findManyToManyRowset($matchTable, $intersectionTable, $callerRefRule = null, $matchRefRule = null, Zend_Db_Table_Select $select = null) {
		$db = $this->_getTable()->getAdapter();
		
		if(is_string($intersectionTable)) {
			$intersectionTable = $this->_getTableFromString($intersectionTable);
		}
		
		if(!$intersectionTable instanceof system_db_table_abstract) {
			$type = gettype($intersectionTable);
			if(is_object($intersectionTable)) {
				$type = get_class($intersectionTable);
			}
			throw new system_db_table_row_exception("Intersection table must be a db_table_abstract, but it is $type");
		}
		
		// even if we are interacting between a table defined in a class and a
		// table via extension, ensure to persist the definition
		if(($tableDefinition = $this->_table->getDefinition())!==null&&($intersectionTable->getDefinition()==null)) {
			$intersectionTable->setOptions(array(system_db_table_abstract::DEFINITION => $tableDefinition));
		}
		
		if(is_string($matchTable)) {
			$matchTable = $this->_getTableFromString($matchTable);
		}
		
		if(!$matchTable instanceof system_db_table_abstract) {
			$type = gettype($matchTable);
			if(is_object($matchTable)) {
				$type = get_class($matchTable);
			}
			throw new system_db_table_row_exception("Match table must be a Db_Table_Abstract, but it is $type");
		}
		
		// even if we are interacting between a table defined in a class and a
		// table via extension, ensure to persist the definition
		if(($tableDefinition = $this->_table->getDefinition())!==null&&($matchTable->getDefinition()==null)) {
			$matchTable->setOptions(array(system_db_table_abstract::DEFINITION => $tableDefinition));
		}
		
		if($select===null) {
			$select = $matchTable->select();
		} else {
			$select->setTable($matchTable);
		}
		
		// Use adapter from intersection table to ensure correct query construction
		$interInfo = $intersectionTable->info();
		$interDb = $intersectionTable->getAdapter();
		$interName = $interInfo['name'];
		$interSchema = isset($interInfo['schema']) ? $interInfo['schema'] : null;
		$matchInfo = $matchTable->info();
		$matchName = $matchInfo['name'];
		$matchSchema = isset($matchInfo['schema']) ? $matchInfo['schema'] : null;
		
		$matchMap = $this->_prepareReference($intersectionTable, $matchTable, $matchRefRule);
		
		for($i = 0; $i<count($matchMap[system_db_table_abstract::COLUMNS]); ++$i) {
			$interCol = $interDb->quoteIdentifier('i'.'.'.$matchMap[system_db_table_abstract::COLUMNS][$i], true);
			$matchCol = $interDb->quoteIdentifier('m'.'.'.$matchMap[system_db_table_abstract::REF_COLUMNS][$i], true);
			$joinCond[] = "$interCol = $matchCol";
		}
		$joinCond = implode(' AND ', $joinCond);
		
		$select->from(array('i' => $interName), array(), $interSchema)->joinInner(array('m' => $matchName), $joinCond, system_db_select::SQL_WILDCARD, $matchSchema)->setIntegrityCheck(false);
		
		$callerMap = $this->_prepareReference($intersectionTable, $this->_getTable(), $callerRefRule);
		
		for($i = 0; $i<count($callerMap[system_db_table_abstract::COLUMNS]); ++$i) {
			$callerColumnName = $db->foldCase($callerMap[system_db_table_abstract::REF_COLUMNS][$i]);
			$value = $this->_data[$callerColumnName];
			$interColumnName = $interDb->foldCase($callerMap[system_db_table_abstract::COLUMNS][$i]);
			$interCol = $interDb->quoteIdentifier("i.$interColumnName", true);
			$interInfo = $intersectionTable->info();
			$type = $interInfo['metadata'][$interColumnName]['DATA_TYPE'];
			$select->where($interDb->quoteInto("$interCol = ?", $value, $type));
		}
		
		$stmt = $select->query();
		
		$config = array('table' => $matchTable, 'data' => $stmt->fetchAll(system_db::FETCH_ASSOC), 'rowClass' => $matchTable->getRowClass(), 'readOnly' => false, 'stored' => true);
		
		$rowsetClass = $matchTable->getRowsetClass();
		$rowset = new $rowsetClass($config);
		return $rowset;
	}
	
	public function __call($method, array $args) {
		$matches = array();
		
		if(count($args)&&$args[0] instanceof system_db_table_select) {
			$select = $args[0];
		} else {
			$select = null;
		}
		
		/**
		 * Recognize methods for Has-Many cases:
		 * findParent<Class>()
		 * findParent<Class>By<Rule>()
		 * Use the non-greedy pattern repeat modifier e.g. \w+?
		 */
		if(preg_match('/^findParent(\w+?)(?:By(\w+))?$/', $method, $matches)) {
			$class = $matches[1];
			$ruleKey1 = isset($matches[2]) ? $matches[2] : null;
			return $this->findParentRow($class, $ruleKey1, $select);
		}
		
		/**
		 * Recognize methods for Many-to-Many cases:
		 * find<Class1>Via<Class2>()
		 * find<Class1>Via<Class2>By<Rule>()
		 * find<Class1>Via<Class2>By<Rule1>And<Rule2>()
		 * Use the non-greedy pattern repeat modifier e.g. \w+?
		 */
		if(preg_match('/^find(\w+?)Via(\w+?)(?:By(\w+?)(?:And(\w+))?)?$/', $method, $matches)) {
			$class = $matches[1];
			$viaClass = $matches[2];
			$ruleKey1 = isset($matches[3]) ? $matches[3] : null;
			$ruleKey2 = isset($matches[4]) ? $matches[4] : null;
			return $this->findManyToManyRowset($class, $viaClass, $ruleKey1, $ruleKey2, $select);
		}
		
		/**
		 * Recognize methods for Belongs-To cases:
		 * find<Class>()
		 * find<Class>By<Rule>()
		 * Use the non-greedy pattern repeat modifier e.g. \w+?
		 */
		if(preg_match('/^find(\w+?)(?:By(\w+))?$/', $method, $matches)) {
			$class = $matches[1];
			$ruleKey1 = isset($matches[2]) ? $matches[2] : null;
			return $this->findDependentRowset($class, $ruleKey1, $select);
		}
		
		throw new system_db_table_row_exception("Unrecognized method '$method()'");
	}
	
	protected function _getTableFromString($tableName) {
		
		if($this->_table instanceof system_db_table_abstract) {
			$tableDefinition = $this->_table->getDefinition();
			
			if($tableDefinition!==null&&$tableDefinition->hasTableConfig($tableName)) {
				return new system_db_table($tableName, $tableDefinition);
			}
		}
		
		$options = array();
		
		if(!!($table = $this->_getTable())) {
			$options['db'] = $table->getAdapter();
		}
		
		if(isset($tableDefinition)&&$tableDefinition!==null) {
			$options[system_db_table_abstract::DEFINITION] = $tableDefinition;
		}
		
		return new $tableName($options);
	}
}